package com.zwcl.upms.wechat.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zwcl.common.core.constant.CacheConstants;
import com.zwcl.common.core.constant.Constants;
import com.zwcl.common.core.domain.entity.ApiResult;
import com.zwcl.common.core.domain.entity.CacheUser;
import com.zwcl.common.core.enums.AppCodeEnum;
import com.zwcl.common.core.enums.ExceptionCode;
import com.zwcl.common.core.enums.RequestEndpointEnum;
import com.zwcl.common.core.exception.BusinessException;
import com.zwcl.common.core.redis.RedisService;
import com.zwcl.common.core.utils.*;
import com.zwcl.common.core.utils.http.RestTemplateHelper;
import com.zwcl.upms.wechat.api.dto.BindPhoneDto;
import com.zwcl.upms.wechat.api.entity.CacheWxUser;
import com.zwcl.upms.wechat.api.entity.WxUser;
import com.zwcl.upms.wechat.properties.AuthProperties;
import com.zwcl.upms.wechat.properties.MiniProgramProperties;
import com.zwcl.upms.wechat.service.WxLoginAuthService;
import com.zwcl.upms.wechat.service.WxUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Created by Administrator on 2018/9/19.
 * TODO：这里要加缓存处理
 */
@Slf4j
@Service
public class WxLoginAuthServiceImpl implements WxLoginAuthService {
    /**
     * 服务器第三方session有效时间，单位秒, 默认1天
     */
    private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60;

    @Autowired
    private AuthProperties authProperties;

    @Autowired
    RestTemplateHelper restTemplateHelper;

    //@Autowired
    //private RedisService redisService;

    @Resource(name = "stringRedisTemplate")
    private ValueOperations<String, String> sops;

    @Autowired
    private WxUserService wxUserService;

    /**
     * 获取某个应用小程序的配置
     * TODO:这个转换，可能在大批量登录时，影响效率
     * @param appCode
     * @return
     */
    private MiniProgramProperties appAuthProperties(String appCode){
        List<MiniProgramProperties> miniProgramPropertiesList=authProperties.getMiniPrograms();
        Map<String,MiniProgramProperties> miniProgramPropertiesMaps= miniProgramPropertiesList.stream().collect(Collectors.toMap((p) -> p.getAppCode(), (p) -> p));
        return miniProgramPropertiesMaps.get(appCode);
    }

    //code换微信openId和session_key
    @Override
    public JSONObject getWxSession(String code, String appCode) {
        MiniProgramProperties miniProgramProperties=appAuthProperties(appCode);
        String appId= miniProgramProperties.getAppId();
        String secret = miniProgramProperties.getAppSecret();
        String url=String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=%s",authProperties.getSessionHost(),appId, secret,code,authProperties.getGrantType());
        JSONObject result= (JSONObject) restTemplateHelper.getForObject(url);
        return result;
    }

    @Override
    public String getAccessToken(String appCode) {
        MiniProgramProperties miniProgramProperties=appAuthProperties(appCode);
        String appId= miniProgramProperties.getAppId();
        String secret = miniProgramProperties.getAppSecret();
        String url=String.format("%s?appid=%s&secret=%s&grant_type=%s",authProperties.getAccessTokenUrl(),appId, secret,"client_credential");
        JSONObject result= (JSONObject) restTemplateHelper.getForObject(url);
        String accessToken = result.getString("access_token");
        return accessToken;
    }

    //微信登录对外接口
    //加上token参数
    @Override
    public ApiResult wechatLogin(String code, String appCode) {
        String token="";
        AppCodeEnum codeEnum= AppCodeEnum.of(appCode);
        //获取唯一的用户信息
        JSONObject wxchatSession=getWxSession(code,appCode);
        if(wxchatSession.getString("errcode")!=null){
            String errMsg=wxchatSession.getString("errmsg");
            log.error("微信登录失败，{}",errMsg);
            return ApiResult.fail(errMsg);
        }
        String openId=  wxchatSession.getString("openid");
        String unionId = wxchatSession.getString("unionid ");
        String sessionKey= wxchatSession.getString("session_key");
        //String openId="test_test_openId";
        //String sessionKey="test_test_session_key"+new Date().toString();
        //初始化保存用户
        WxUser wxUser=wxUserService.findWxUserByOpenId(openId);
        if(wxUser==null){                                    //新用户
            wxUser=new WxUser();
            wxUser.setThirdId(openId);
            if(StringUtils.isNotBlank(unionId)){
                wxUser.setUnionId(unionId);
            }
            //生成唯一的token，和appCode一起加密
            token= MD5Utils.getMd5(openId+"#"+appCode+"#"+sessionKey);
            wxUser.setUserToken(token);
            wxUser.setSessionKey(sessionKey);
            wxUser.setLastLoginTime(LocalDateTime.now());
            wxUser.setAppCode(AppCodeEnum.of(appCode).getValue());
            wxUser.setAppType(AppCodeEnum.of(appCode).getAppType());
            //设置用户程序类型
            wxUserService.save(wxUser);
        }else {                                              //老用户，但是有可能登录多次
            //重新生成新的token
            token= MD5Utils.getMd5(openId+"#"+appCode+"#"+sessionKey);
            wxUser.setSessionKey(sessionKey);
            wxUser.setLastLoginTime(LocalDateTime.now());
            wxUser.setUserToken(token);
            wxUserService.updateById(wxUser);
        }
        if(wxUser==null)
            return ApiResult.fail("获取token失败");
        //保存用户数据到缓存中
        CacheWxUser cacheWxUser=new CacheWxUser();
        cacheWxUser.setOpenId(openId);
        cacheWxUser.setSessionKey(sessionKey);
        cacheWxUser.setUserId(Long.valueOf(wxUser.getId()));
        cacheWxUser.setFrom(RequestEndpointEnum.C1.getCode());
        cacheWxUser.setAppCode(appCode);
        //设置缓存，加上过期时间，存储纯Json字符串，方便网关校验时，不依赖任何实体。
        sops.set(getTokenKey(token), JsonUtils.objectToJson(cacheWxUser),EXPIRE_TIME, TimeUnit.SECONDS);
        //redisService.setCacheObject(getTokenKey(token),cacheWxUser,EXPIRE_TIME,TimeUnit.SECONDS);
        JSONObject ret = new JSONObject();
        ret.put("token", token);
        return ApiResult.ok(ret);
    }

    //这个校验过程，有可能是解密然后进行数据库校验
    @Override
    public ApiResult checkLogin(String token) {
        String cacheWxUser = sops.get(getTokenKey(token));
        if(!StringUtils.isBlank(cacheWxUser)){
            return ApiResult.ok(true);
        }else {
//            WxUser wxUser = wxUserService.findWxUserByToken(token);
//            if (wxUser != null) {
//                return ApiResult.ok(true);
//            } else {
//                return ApiResult.ok(false);
//            }
            return ApiResult.fail("token已失效",false);
        }
    }

    @Override
    public ApiResult bindPhone(BindPhoneDto dto) {
        //通过token拿sessionKey
        WxUser wxUser=wxUserService.findWxUserByToken(dto.getUserToken());
        if(wxUser==null){
            return ApiResult.fail("用户未登录授权！");
        }
        String sessionKey=wxUser.getSessionKey();
        //根据sessionKey解密出用户手机号码
        JSONObject phoneInfo = WxUtil.decrypt(dto.getEncryptedData(), sessionKey, dto.getIv());
        if (phoneInfo == null) {
            log.error("bindPhone error. decrypt failed. request={}", JSON.toJSONString(dto));
            return ApiResult.fail("获取手机号失败！");
        }
        String wxPhone = phoneInfo.getString("phoneNumber");
        wxUser.setPhone(wxPhone);
        //保存到数据库
        try {
            wxUserService.updateById(wxUser);
            JSONObject ret =new JSONObject();
            ret.put("phone",wxPhone);
            return ApiResult.ok(ret);
        }catch (Exception e){
            return ApiResult.fail("获取失败！");
        }
    }

    //解密获取用户信息
    @Override
    public ApiResult getDecodeUser(String iv, String encryptedData, String token,String appCode){
        //查询看数据库是否有
        try {
            WxUser wxUserMongo = wxUserService.findWxUserByToken(token);
            if(wxUserMongo==null){
                return ApiResult.fail("用户还未授权登录，无法获取用户信息！");
            }
            if (StringUtils.isBlank(wxUserMongo.getNickName())) {          //曾经没有解密存储过
                WxUser user = decodeUserInfo(iv, encryptedData, wxUserMongo.getSessionKey());
                wxUserMongo.setNickName(user.getNickName());
                wxUserMongo.setAvatarUrl(user.getAvatarUrl());
                wxUserMongo.setCity(user.getCity());
                wxUserMongo.setCountry(user.getCountry());
                wxUserMongo.setGender(user.getGender());
                wxUserMongo.setProvince(user.getProvince());
                wxUserMongo.setUnionId(user.getUnionId());
                wxUserMongo.setLastLoginTime(wxUserMongo.getLastLoginTime());
                //保存最新信息到数据库
                wxUserService.updateById(wxUserMongo);
            }
            //返回时，敏感信息openId，UnionId擦除
            wxUserMongo.setThirdId("");
            wxUserMongo.setUnionId("");
            wxUserMongo.setSessionKey("");
            wxUserMongo.setPhone("");
            return ApiResult.ok(wxUserMongo);
        }catch (Exception e){
            log.error("用户解密失败. err={}", e);
            return ApiResult.fail("用户信息获取失败！");
        }
    }

    //解密用户信息
    private WxUser decodeUserInfo(String iv, String encryptedData, String sessionKey)
    {
        try {
            String result = AesCbcUtil.decrypt(encryptedData, sessionKey, iv, "UTF-8");
            if (null != result && result.length() > 0) {
                WxUser user=new WxUser();
                JSONObject userInfoJSON = JSONObject.parseObject(result);
                user.setThirdId(userInfoJSON.get("openId").toString());
                user.setNickName(userInfoJSON.get("nickName").toString());
                user.setGender(Integer.valueOf(userInfoJSON.get("gender").toString()));
                user.setCity(userInfoJSON.get("city").toString());
                user.setProvince(userInfoJSON.get("province").toString());
                user.setCountry(userInfoJSON.get("country").toString());
                user.setAvatarUrl(userInfoJSON.get("avatarUrl").toString());
                if(userInfoJSON.get("unionId")!=null){
                    user.setUnionId(userInfoJSON.get("unionId").toString());
                }
                return user;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private String getTokenKey(String token){
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }
}
